// Créé avec XmlCppClassGenerator version 2.0.

#include "OptimiseurEquipement.hpp"

#include "Core/CalculateurReussite.hpp"

#include "Core/BonusEquipement.hpp"

#include "Utils/Preferences.hpp"

#include "Utils/Combinaison.hpp"

#include "QtCore/QDebug"

QHash< unsigned, OptimiseurEquipement::Bonus > OptimiseurEquipement::EquipementExistant =
QHash< unsigned, OptimiseurEquipement::Bonus >();

unsigned OptimiseurEquipement::SeuilReussite( 0 );

void OptimiseurEquipement::initMembresStatiques()
{
    if ( EquipementExistant.size() > 0 )
        return;

    SeuilReussite = 100u;//Preferences::instance() -> gainMinimal();

    /**
      * En premier apparaissent les équipement permanents.
      */
    EquipementExistant.insert(  1, Bonus( CarSadisme    , 1u, 1000u, true ) );
    EquipementExistant.insert(  2, Bonus( CarSadisme    , 2u, 2000u, true ) );
    EquipementExistant.insert(  4, Bonus( CarLaideur    , 1u, 1000u, true ) );
    EquipementExistant.insert(  5, Bonus( CarLaideur    , 2u, 2000u, true ) );
    EquipementExistant.insert(  7, Bonus( CarForce      , 1u, 1000u, true ) );
    EquipementExistant.insert(  8, Bonus( CarForce      , 2u, 2000u, true ) );
    EquipementExistant.insert( 11, Bonus( CarGourmandise, 1u, 1000u, true ) );
    EquipementExistant.insert( 12, Bonus( CarGourmandise, 2u, 2000u, true ) );
    EquipementExistant.insert( 14, Bonus( CarControle   , 1u, 1000u, true ) );
    EquipementExistant.insert( 15, Bonus( CarControle   , 2u, 2000u, true ) );

    /**
      * Puis les équipement temporaires.
      */
    EquipementExistant.insert(  0, Bonus( CarSadisme    , 1u, 200u ) );
    EquipementExistant.insert(  3, Bonus( CarLaideur    , 1u, 200u ) );
    EquipementExistant.insert(  6, Bonus( CarForce      , 1u, 200u ) );
    EquipementExistant.insert(  9, Bonus( CarGourmandise, 1u, 200u ) );
    EquipementExistant.insert( 10, Bonus( CarGourmandise, 2u, 500u ) );
    EquipementExistant.insert( 13, Bonus( CarControle   , 1u, 200u ) );
}

OptimiseurEquipement::OptimiseurEquipement()
    : ValeursContrat( 0 ),
      ValeursMonstre( 0 ),
      EquipementPossible( ),
      EvalutationEquipement( ),
      Calculateur( new CalculateurReussite() )
{
    OptimiseurEquipement::initMembresStatiques();
    qDebug() << "Seuil réussite :" << SeuilReussite;
}

OptimiseurEquipement::~OptimiseurEquipement()
{
    delete Calculateur;
}

const QList< unsigned >* OptimiseurEquipement::operator()( const XmlInfoContrat& contrat,
                                                           const XmlInfoMonstre& monstre )
{
    ValeursContrat = new XmlInfoContrat( contrat );
    ValeursMonstre = new XmlInfoMonstre( monstre );

    QHash< EnumCaracteristique, unsigned > probElementaires;
    unsigned probabilite;

    probabilite = Calculateur->operator()( contrat, monstre, probElementaires );

    if ( probabilite >= OptimiseurEquipement::SeuilReussite )
        return 0;

    QHashIterator< unsigned, Bonus > iterateur( EquipementExistant );

    EquipementPossible.clear();

    while ( iterateur.hasNext() )
    {
        iterateur.next();

        /**
          * Si l'équipement en question est déjà utilisé, alors ce n'est plus
          * possible de l'équiper à nouveau.
          */
        if ( ValeursMonstre -> equipementPermanent() -> contains( iterateur.key() ) ||
             ValeursMonstre -> equipementTemporaire() -> contains( iterateur.key() ) )
            continue;

        /**
          * Si l'équipement courant améliore le contrôle du monstre et que le
          * contrat requiert de la gourmandise, alors il est inutile d'ajouter
          * cet équipement dans l'équipement possible.
          */
        if ( iterateur.value().caracteristique() == CarControle &&
             ValeursContrat -> caracteristique( CarGourmandise ) > 0u &&
             ( ValeursContrat -> typeContrat() != Monstrueux  ||
               ValeursContrat -> typeContrat() != Infernal ) )
            continue;

        if ( iterateur.value().caracteristique() != CarControle &&
             probElementaires[ iterateur.value().caracteristique() ] != 100u )
        {
            /**
              * L'équipement n'ajoute pas de contrôle au monstre, et la
              * caractéristique associée n'est pas au maximum, il faut donc
              * prendre en compte cet équipement.
              */
            EquipementPossible.append( iterateur.key() );
            iterateur.value().affiche();
        }
        else if ( iterateur.value().caracteristique() == CarControle &&
                  probElementaires[ CarGourmandise ] != 100u )
        {
            /**
              * L'équipement ajoute du contrôle au monstre, et le score de
              * gourmandise n'est pas au maximum, il faut donc prendre en compte
              * cet équipement.
              */
            EquipementPossible.append( iterateur.key() );
            iterateur.value().affiche();
        }
    }

    QList< unsigned > uneCombinaison;

    unsigned i, tot( EquipementPossible.size() );

    typedef void ( OptimiseurEquipement::*fctPtr )( const QList< unsigned >::iterator &debut,
                                                    const QList< unsigned >::iterator &fin );
    fctPtr fonction = &OptimiseurEquipement::traiteEquipement;

    for ( i = 1u; i <= tot; i++ )
    {
        uneCombinaison.append( -1u );
        //qDebug() << "Test des combinaisons avec" << uneCombinaison.size() << "éléments";
        combinaison< QList< unsigned >::iterator, OptimiseurEquipement >( EquipementPossible.begin(),
                                                                          EquipementPossible.end(), 0,
                                                                          uneCombinaison.begin(),
                                                                          uneCombinaison.end(), 0,
                                                                          tot - i, fonction, this );
    }

    const QList< unsigned > &optimum = trouveEquipementOptimal();

    delete ValeursContrat;
    delete ValeursMonstre;
    ValeursContrat = 0;
    ValeursMonstre = 0;

    return &optimum;
}

void OptimiseurEquipement::traiteEquipement( const QList< unsigned >::iterator &debut,
                                             const QList< unsigned >::iterator &fin )
{
    QList< unsigned >::iterator it( debut );

    QString s;

    unsigned prix( 0u ), prob;

    /**
      * Pour chaque pièce d'équipement à traiter, on applique le bonus à la
      * caractéristique du monstre. En même temps on calcule le coût de
      * l'équipement traité, @e sans prendre en compte le coût de l'équipement
      * permanent, ce qui permet de privilégier ce type d'équipement.
      */
    for ( it = debut; it != fin; it++ )
    {
        const Bonus &ref = EquipementExistant[ *it ];
        ValeursMonstre -> rCaracteristique( ref.caracteristique() ) += ref.valeurBonus();
        if ( ! ref.permanent() )
            prix += ref.prix();
    }

    /**
      * On calcule la probabilité pour le monstre de remplir le contrat.
      */
    prob = Calculateur -> operator ()( *ValeursContrat, *ValeursMonstre );

    s += QString::number( prob ) + " ";
    s += QString::number( prix ) + " (";

    /**
      * Finalement on déduit les bonus prodigués par l'équipement, afin de
      * pouvoir recommancer avec la combinaison suivante.
      */
    for ( it = debut; it != fin; it++ )
    {
        const Bonus &ref = EquipementExistant[ *it ];
        ValeursMonstre -> rCaracteristique( ref.caracteristique() ) -= ref.valeurBonus();
        s += QString::number( *it );
        s += ' ';
    }

    s += ")";

    //qDebug() << s;

    BiClef clef( prob, prix );
    QList< unsigned > equip;

    for ( it = debut; it != fin; it++ )
    {
        equip << *it;
    }

    EvalutationEquipement.insertMulti( clef, equip );
}

const QList< unsigned >& OptimiseurEquipement::trouveEquipementOptimal() const
{
    QMapIterator< BiClef, QList< unsigned > > sherlock( EvalutationEquipement );

    while ( sherlock.hasNext() )
    {
        sherlock.next();
//        qDebug() << sherlock.key().primaire() << sherlock.key().secondaire()
//                    << sherlock.value();
        if ( sherlock.key().primaire() > SeuilReussite )
            return sherlock.value();
    }

    unsigned reussite( sherlock.key().primaire() );

    while ( sherlock.hasPrevious() &&
            sherlock.peekPrevious().key().primaire() == reussite )
    {
        sherlock.previous();
    }
    return sherlock.value();
}
